<template>
  <ExamPaper
    :items="paper"
    :name="title"
    @update:answers="val => (finalAnswers = val)"
  />
</template>

<script setup>
import { ref, onMounted, computed } from 'vue'
import ExamPaper from '@/components/ExamPaper.vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const title =  computed(() => {
  return route.meta.title  || '算法题库'
})
const paper = ref([
    {
    question: 
    `
    90. 子集 II
    中等
    相关标签
    premium lock icon
    相关企业
    给你一个整数数组 nums ，其中可能包含重复元素，请你返回该数组所有可能的 子集（幂集）。

    解集 不能 包含重复的子集。返回的解集中，子集可以按 任意顺序 排列。

    

    示例 1：

    输入：nums = [1,2,2]
    输出：[[],[1],[1,2],[1,2,2],[2],[2,2]]
    示例 2：

    输入：nums = [0]
    输出：[[],[0]]
    `,
    answer:
    `
    // ai 自动生成的代码
    function dealByAi(nums) {
      const length = nums.length
      const res = []
      const path = []
      nums.sort((a, b) => a - b) // 先排序
      const used = new Array(length).fill(false)
      function backtracking(startIndex) {
          res.push([...path])
          if (startIndex >= length) return
          for (let i = startIndex; i < length; i++) {
              if (i > startIndex && nums[i] === nums[i - 1] && !used[i - 1]) continue // 去重
              path.push(nums[i])
              used[i] = true
              backtracking(i + 1)
              path.pop()
              used[i] = false
          }
      }
      backtracking(0)
      console.log(res);
      return res
    }
    deal([1,2,2])
    // 我自己写的代码
    function dealMyself(nums) {
      nums.sort((a,b) => a - b) // 先排序
      const length = nums.length
      const res = []
      for (let i = 0; i <= length; i++) { // 0,1,2,3
        // 用递归的方法做，i表示递归的层数
        function backtracking(hasUse = [], canUse = [...nums]) {
          if (hasUse.length === i) {
            res.push([...hasUse])
            return
          }  
          for (let j = 0; j < canUse.length; j++) {
            // if (j > 0 && canUse[j] === canUse[j - 1] && !hasUse.includes(canUse[j - 1])) continue //
            // hasUse.push(canUse[j])
            // const nextCanUse = canUse.slice(j + 1) // 下一层可用的元素
            // backtracking(hasUse, nextCanUse)
            // hasUse.pop() // 回溯
            // 为什么不回溯canUse呢？因为canUse是每一层新传入的参数
            // 那为什么回溯hasUse呢？因为hasUse是每一层传入的参数，是同一个引用
            // 也就是说，canUse每一层都是一个新的数组，而hasUse是同一个数组
            // 这样就保证了每一层的canUse都是正确的

            if (j > 0 && canUse[j] === canUse[j - 1]) continue //

            const newHasUse = [...hasUse,canUse[j]]
            const newCanUse = [...canUse].slice(j+1)
            backtracking(newHasUse, newCanUse)
          }
        }
        backtracking()
      }
      console.log(res);
      return res
    }
    dealMyself([1,2,2])
    // 为什么要先for循环长度，然后递归去做。 其实用最长的长度去做，自然而然会把每层的值返回回来
    function dealMyself(nums) {
      nums.sort((a, b) => a - b) // 先排序
      const res = []
      // 用递归的方法做，i表示递归的层数
      function backtracking(hasUse = [], canUse = [...nums]) {
        res.push([...hasUse])
        for (let j = 0; j < canUse.length; j++) {
          if (j > 0 && canUse[j] === canUse[j - 1]) continue //
          const newHasUse = [...hasUse, canUse[j]]
          const newCanUse = [...canUse].slice(j + 1)
          backtracking(newHasUse, newCanUse)
        }
      }
      backtracking()
      console.log(res);
      return res
    }
    
    dealMyself([1, 2, 2])
    `,
    remark: 
    `
    AI 版确实比你的第一版简洁、高效，但**“好”主要体现在写法习惯上**，而不是逻辑深度——它把“层数循环”和“canUse 数组”这两个多余概念全部拿掉了，直接用 \`startIndex\` 一把梭。下面给你逐条对比，一眼就能看出差距。

    ---

    ### 1. 结构复杂度
    | 维度 | AI 版 | 你的版 |
    |---|---|---|
    | 循环层数 | 只有一层 \`for\` | 外层 \`for (i…)\` + 内层 \`for (j…)\` |
    | 参数个数 | 1 个 (\`startIndex\`) | 2 个 (\`hasUse\`, \`canUse\`) |
    | 拷贝次数 | 0 次深拷贝（\`path\` 用 \`push/pop\` 回溯） | 每层 2 次深拷贝（\`newHasUse\`、\`newCanUse\`） |
    | 辅助数组 | 1 个布尔数组 \`used\` | 0（你后来去掉了 \`used\` 逻辑） |

    ---

    ### 2. 去重正确性
    - **AI 版**  
      \`i > startIndex && nums[i] === nums[i-1] && !used[i-1]\`  
      经典“树层去重”模板，**同一层**遇到重复值直接跳过，安全、通用、易读。

    - **你的版**  
      \`j > 0 && canUse[j] === canUse[j-1]\`  
      这里**碰巧**也能剪干净，是因为 \`canUse\` 每次都从 \`j+1\` 截断，**隐式地**保证了同一层不会二次遇见相同值；  
      但**可读性低**，别人第一眼看不出“为什么这样就能去重”。

    ---

    ### 3. 时空开销
    - **AI 版**  
      全程**只维护一个 \`path\` 数组**，\`push/pop\` 回溯，无额外深拷贝。  
      时间、空间都是 **O(2^n * n)** 标准级别。

    - **你的版**  
      每层递归**新建两个数组**：  
      \`newHasUse = [...hasUse, x]\`  
      \`newCanUse = canUse.slice(j+1)\`  
      时间、空间都多乘一个 **n**，实测 n=20 就能差出几十倍。

    ---

    ### 4. 可维护性
    AI 版 6 行核心循环，**一眼看懂**；  
    你的版需要读者先理解“外层 i 是长度、内层 j 是下标、canUse 被截断”这套**自定义规则**，**心智负担高**。

    ---

    ### 5. 一句话总结
    AI 版把“选或不选”抽象成 **startIndex 向后移动**，用 **used 数组**显式去重，**无深拷贝、无外层长度循环、模板化、可复现**；  
    你的版虽然能跑对，但靠“隐式截断”去重、靠“深拷贝”传参，**效率低、模板化程度差**，面试/工程场景都容易被追问。
    `
  },
  {
    question: 
    `
    236. 二叉树的最近公共祖先
    中等
    相关标签
    premium lock icon
    相关企业
    给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

    百度百科中最近公共祖先的定义为：“对于有根树 T 的两个节点 p、q，最近公共祖先表示为一个节点 x，满足 x 是 p、q 的祖先且 x 的深度尽可能大（一个节点也可以是它自己的祖先）。”

    

    示例 1：


    输入：root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
    输出：3
    解释：节点 5 和节点 1 的最近公共祖先是节点 3 。
    示例 2：


    输入：root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
    输出：5
    解释：节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
    示例 3：

    输入：root = [1,2], p = 1, q = 2
    输出：1
    

    提示：

    树中节点数目在范围 [2, 105] 内。
    -109 <= Node.val <= 109
    所有 Node.val 互不相同 。
    p != q
    p 和 q 均存在于给定的二叉树中。
    `,
    answer:
    `
    var lowestCommonAncestor = function(root, p, q) {
        console.log(root)
        const copyRoot = [].concat(root);
        const deepsArr = [];
        const maps = new Map();
        function deepDeal (deep = 0){
            const length = 2**deep
            const deepArr = [];
            for(let i = 0;i<length;i++){
                if(!copyRoot.length){break}
                let value = copyRoot.splice(0,1)[0];
                const data = {value,deep,ds:i%2}
                deepArr.push(data)
                if(deep!=0){
                    data.parent = deepsArr[deep-1][Math.floor(i/2)]
                }
                if(value!==null){
                    maps.set(value,data)
                }
            }
            deepsArr.push(deepArr)
            copyRoot.length&&deepDeal(deep+1)
        }
        deepDeal(0)
        const a = maps.get(p);
        const b = maps.get(q);
        function getParent (item){
            return item.parent
        }
        let data1,data2;
        if(a.deep>b.deep){
            const chaju = a.deep - b.deep;
            data1 = a;
            data2 = b
            for(i=0;i<chaju;i++){
                data1 = getParent(data1)
            }
            if(data1 === data2){
                return data1.value
            }
        }else if(b.deep>a.deep){
            const chaju = b.deep - a.deep;
            data1 = a;
            data2 = b
            for(i=0;i<chaju;i++){
                data2 = getParent(data2)
            }
            if(data1 === data2){
                return data1.value
            }
        }else{
            data1 = a;
            data2 = b
        }
        while(data1.parent != data2.parent){
            data1 = getParent(data1);
            data2 = getParent(data2);
        }
        return data1.parent.value
    };
    lowestCommonAncestor([3,5,1,6,2,0,8,null,null,7,4],5,4)
    `,
    remark: 
    `
    对，LeetCode 的注释只写了 TreeNode，但没给你定义，因为：
    TreeNode 是 LeetCode 全局内置的类，你在编辑器里看不到源码，但运行时它就在那里。
    ✅ 官方定义（等价于）
    JavaScript
    复制
    function TreeNode(val, left, right) {
        this.val   = val   === undefined ? 0 : val;
        this.left  = left  === undefined ? null : left;
        this.right = right === undefined ? null : right;
    }
    也就是说：
    没有 parent、deep 这些属性；
    只有 val、left、right；
    p、q 也是 TreeNode 实例，不是值！
    复制
    分享
    实际[3,5,1,6,2,0,8,null,null,7,4] 用teeNode展示是什么样子
    复制
    把 [3,5,1,6,2,0,8,null,null,7,4] 按层序还原成一棵 TreeNode 树，长这样：
    复制
            3
          / \
          5   1
        / \ / \
        6  2 0  8
          / \
        7   4
    对应的 TreeNode 对象（只画关键字段）：
    JavaScript
    复制
    TreeNode {
      val: 3,
      left: TreeNode {
        val: 5,
        left: TreeNode { val: 6, left: null, right: null },
        right: TreeNode {
          val: 2,
          left: TreeNode { val: 7, left: null, right: null },
          right: TreeNode { val: 4, left: null, right: null }
        }
      },
      right: TreeNode {
        val: 1,
        left: TreeNode { val: 0, left: null, right: null },
        right: TreeNode { val: 8, left: null, right: null }
      }
    }
    ✅ 一句话
    null 表示“没有孩子”，不会生成节点；TreeNode 只有 val、left、right 三个字段。
    复制
    分享
    意思是它已经给我一个树状结构了，已经不需要我构建了是吧
    复制
    完全正确！
    LeetCode 已经把 [3,5,1,6,2,0,8,null,null,7,4] build 成一棵现成的 TreeNode 树；
    root 变量就是树根（TreeNode 实例）；
    p 和 q 也是树里某个节点的引用（TreeNode 实例）；
    你 不用再解析数组、不再用 Map、不再自己找 parent；
    直接对 TreeNode 的 val、left、right 做递归或迭代即可。
    ✅ 一句话
    树已经摆好，你就写“在树上找 LCA”的逻辑就行。
    复制
    分享
    好坑啊
    编辑
    复制
    确实！LeetCode 的“坑”就在于：
    网页用例给你看的是数组（方便你复制粘贴调试）；
    实际运行时传的是 TreeNode（还藏了源码）；
    注释里只写 TreeNode，不告诉你结构；
    很多新手（包括曾经的我）一开始都以为要自己去摊平数组，结果越写越复杂，最后还报错。
    ✅ 小建议
    本地调试时，自己写个 buildTree 函数，把数组转成 TreeNode，就能和 LeetCode 行为一致；
    提交时，忘掉数组，只认 val、left、right；
    递归模板 5 行就够，越简单越不容易踩坑。
    ✅ 一句话总结
    LeetCode 的“可视化输入”只是化妆，真正的 root 是 TreeNode 对象——记住这一点，以后再也不被坑！

    草了，写大半天，结果被leetCode的数据类型坑了，其实远没有我上面写的那么复杂。只是一个二叉树遍历向上查找的问题
    `,
  }
])
onMounted(()=>{
    // 2 的指数幂

})


const finalAnswers = ref([])   // 实时收集到的答案
</script>